#include "ai_promaster.h"
#include "info.h"
#include "random.h"
#include "pokermath.h"
#include "observer_statkeeper.h"

#include <iostream>
#include <math.h.>

enum ToDo
{
  TD_CHECK,
  TD_FOLD,
  TD_ALLIN,
  TD_CALL,
  TD_RAISE
};

AIProMaster::AIProMaster(double tightness)
: tightness(tightness), N(new Network("wagi.txt"))
{
}

Action AIProMaster::doTurn(const Info& info)
{
  /*
  heads-up

  -use pot equity / odds
  -have a memory, to enable strategies that take multiple turns, such as check-raise
  -try to keep track of playing style of other players, recognise individual players
  -change strategy every now and then to try not to be too predictable
  -detect when an opponent is weak and can be outbluffed
  -...
  */
  

    ObserverStatKeeper* observerStatKeeper = (ObserverStatKeeper*)info.observers[2];
	StatKeeper& statKeeper = observerStatKeeper->getStatKeeper();
	std::string s = statisticsToString(*statKeeper.getPlayerStats("opponent"));
    //std::cout << s << std::endl;
    Action lastAction;
    PlayerInfo oponnentInfo = (PlayerInfo)info.players[1 - info.yourIndex];
    
  double raiseChance = 0.0;
  double allInChance = 0.0;
  int maxWager = 0; //for calling

  int bb = info.getBigBlind();
    
  int raiseAmount = (int)(getRandom()*10*bb);
    
  if(info.round == R_PRE_FLOP)
  {
      if (info.getStack() < 10*bb)
      {
          //maly stack, nie powalczymy tak dlugo wiec tylko allin na farta
          allInChance = 1.0;
          raiseChance = 0.0;
          maxWager = info.getStack() + info.getWager();
      }    
      else
      {         
                
        int group = getSklanskyMalmuthGroup(info.getHoleCards()[0], info.getHoleCards()[1]);
        /*
        if(group == 9) maxWager = 0;
        else if(group == 8 || group == 7)
        {
          maxWager = bb * 4;
          raiseChance = 0.3;
        }
        else if(group == 4 || group == 5 || group == 6)
        {
          maxWager = info.getStack() + info.getWager();
          raiseChance = 0.6;
          allInChance = 0.1;
        }
        else if(group == 2 || group == 1 || group == 3)
        {
          maxWager = info.getStack() + info.getWager();
          raiseChance = 0.65;
          allInChance = 0.25;
        }*/
            if(group == 9) maxWager = 0;
    else if(group == 8 || group == 7) maxWager = bb;
    else if(group == 6 || group == 5)
    {
      maxWager = bb * 4;
      raiseChance = 0.2;
    }
    else if(group == 4 || group == 3)
    {
      maxWager = info.getStack() + info.getWager();
      raiseChance = 0.5;
      allInChance = 0.1;
    }
    else if(group == 2 || group == 1)
    {
      maxWager = info.getStack() + info.getWager();
      raiseChance = 0.5;
      allInChance = 0.2;
    }
    }
  }
  else
  {
    double win, tie, lose;
    int num_opponents = info.getNumActivePlayers() - 1;
    if(info.round == R_FLOP) getWinChanceAgainstNAtFlop(win, tie, lose, info.getHoleCards()[0], info.getHoleCards()[1], info.boardCards[0], info.boardCards[1], info.boardCards[2], num_opponents);
    else if(info.round == R_TURN) getWinChanceAgainstNAtTurn(win, tie, lose, info.getHoleCards()[0], info.getHoleCards()[1], info.boardCards[0], info.boardCards[1], info.boardCards[2], info.boardCards[3], num_opponents);
    else if(info.round == R_RIVER) getWinChanceAgainstNAtRiver(win, tie, lose, info.getHoleCards()[0], info.getHoleCards()[1], info.boardCards[0], info.boardCards[1], info.boardCards[2], info.boardCards[3], info.boardCards[4], num_opponents);
  
/*    
    lastAction = oponnentInfo.lastAction;
    switch(lastAction.command)
    {
    case A_FOLD:
    {
      std::cout <<"OSTATNIA AKCJA OPONENTA: "<< "FOLD" << std::endl;
      break;
    }
    case A_CHECK:
    {
      std::cout <<"OSTATNIA AKCJA OPONENTA: "<< "CHECK" << std::endl;
      break;
    }
    case A_CALL:
    {
      std::cout <<"OSTATNIA AKCJA OPONENTA: "<< "CALL" << std::endl;
      break;
    }
    case A_RAISE:
    {
      std::cout <<"OSTATNIA AKCJA OPONENTA: "<< "RAISE" << std::endl;
      break;
    }
    }
 */   

    if(win > tightness)
    {
      maxWager = info.getStack() + info.getWager();
      raiseChance = 0.5;
      allInChance = 0.3;
                //podbij o srednia uwzgledniajaca wylosowana liczbe i szanse na zwyciestwo w stosunku 1:3
          raiseAmount = (raiseAmount+3*win*10*bb)/4;
    }
    else if(win > tightness - 0.2)
    {
      maxWager = (info.getStack() + info.getWager()) / 3;
      raiseChance = 0.2;
      allInChance = 0.0;
                //podbij o srednia uwzgledniajaca wylosowana liczbe i szanse na zwyciestwo w stosunku 1:2
          raiseAmount = (2*raiseAmount+2*win*10*bb)/4;
    }
    else if(tie > 0.8)
    {
      //large chance to tie (maybe there's a royal flush on the table or so). Never fold in this case!
      raiseChance = 0;
      allInChance = 0;
      maxWager = info.getStack() + info.getWager();
    }
    else //misery... only check or fold
    {
      maxWager = 0;
    }

    if (info.getPotOdds() != INFINITY && win > tightness)
    {
       if (info.getCallAmount()/bb*2 > win*10)
       {
        //ostro pojechal, odpuszczamy
        raiseChance = 0.0;
        allInChance = 0.0;
        maxWager = 0;
       }
       //dobre potoddsy wiec nigdy nie pasuj!               
       else if (win>info.getPotOddsPercentage()-AIProMaster::getTolerance())
       {
        maxWager = info.getStack() + info.getWager();                                                             
        raiseChance = win - 0.25;
        allInChance = 0.1;
        double factor = (getTolerance() + win - info.getPotOddsPercentage() + getRandom() - 0.5);
        if (factor > 0)
        {
          raiseAmount = factor*10;
          raiseAmount *= bb;
        }
       }

    }
  }


  //podjecie decyzji o ruchu
  ToDo actionToDo;
  int amountToDo = 0;
  raiseAmount = raiseAmount/2;
  int minWagerTotal = info.getWager() + info.getCallAmount();
  if(minWagerTotal > info.getWager() + info.getStack()) minWagerTotal = info.getWager() + info.getStack();

  if(minWagerTotal > maxWager)
  {
    Action a = info.getCheckFoldAction();
    if(a.command == A_CHECK)
    {
        actionToDo = TD_CHECK;
        amountToDo = 0;       
    }
    else
    {
        actionToDo = TD_FOLD;
        amountToDo = 0;
    }
  }
  else
  {      
    if(getRandom() < raiseChance)
    {
      actionToDo = TD_RAISE;
      
      if (info.getMinChipsToRaise() > raiseAmount)
      {
           amountToDo = info.getMinChipsToRaise();
      }
      else
      {
          amountToDo = raiseAmount;
      }

      if(amountToDo == 0) amountToDo = info.getMinChipsToRaise();
    }
    else if(getRandom() < allInChance)
    {
      actionToDo = TD_ALLIN;
      amountToDo = info.getAllInAction().amount;
    }
    else
    {
      actionToDo = TD_CALL;
      amountToDo = info.getCallAmount();
    }
  }

  Action myAction;

  //przewidywanie ruchu przeciwnika
  Action foreseenOpponentAction;
  switch(actionToDo)
  {
    case TD_CHECK: {myAction.command=A_CHECK; foreseenOpponentAction = foreseeMove(info, oponnentInfo, myAction, amountToDo); break;}
    case TD_ALLIN: 
    case TD_RAISE: {myAction.command = A_RAISE; foreseenOpponentAction = foreseeMove(info, oponnentInfo, myAction, amountToDo);}
  }
  
  //podjecie ostatecznej decyzji o ruchu
  switch(actionToDo)
  {
                    case TD_CHECK: return info.getCheckFoldAction();
                    case TD_FOLD: return info.getCheckFoldAction();
                    case TD_CALL: return info.getCallAction();
                    case TD_ALLIN: return info.getAllInAction();
                    case TD_RAISE: return info.amountToAction(amountToDo);
                    default: info.getCheckFoldAction(); //anyway
  }
}


std::string AIProMaster::getAIName()
{
  return "ProMaster";
}

void AIProMaster::addPlayerStats(ObserverStatKeeper* observer)
{
}

double AIProMaster::getTolerance()
{
 return (1-tightness)/4;
}

Action AIProMaster::foreseeMove(Info info, PlayerInfo opponentInfo, Action myAction, int amount)
{
    //never use when folding or in pre-flop
    
    Action action;
    
    if(info.round != R_PRE_FLOP)
    {
        //input
        double flop = 0.0;
        double turn = 0.0;
        double river = 0.0;
        double potOddsPercentage = 0.0;
        double committedInThisRound = 0.0;
        
        //output
        double fold = 0.0;
        double call = 0.0;
        double raise = 0.0;
        
        //calculations
        if(info.round == R_FLOP) flop = 1.0;
        else if(info.round == R_TURN) turn = 1.0;
        else if(info.round == R_RIVER) river = 1.0;
        
        /*
            przeciwnik gral juz w tej rundzie jezeli:
            - jestesmy dealerem
            - nie jestesmy dealerem a nasz turn jest wiekszy od 1
        */
        if(info.yourIndex == info.dealer || info.turn > 1) committedInThisRound = 1.0;
                              
        if(myAction.command == A_CHECK) potOddsPercentage = info.getPotOddsPercentage();
        else if(myAction.command == A_RAISE) 
        {
            /*
                amount to liczba pieniedzy ktore dodajemy do puli
                wiec:
                1) pot zwiekszy sie o amount
                2)call amount dla przeciwnika bedzie rowny amount - info.getCallAmount()
            */
            int newPot = info.getPot() + amount;
            int newCallAmount = amount - info.getCallAmount();          
            potOddsPercentage = (double)newCallAmount / ((double)newCallAmount + (double)newPot);
        }
        
        
        //prepare input and output
        vector<double> input;
        input.push_back(flop);
        input.push_back(turn);
        input.push_back(river);
        input.push_back(potOddsPercentage);
        input.push_back(committedInThisRound);
        
        vector<double> output;        
        
        //test
        output = N->test(input);
        if(output.at(0) >= output.at(1) && output.at(0) >= output.at(2)) //fold
        {
            action.command = A_FOLD;
        }
        else if(output.at(1) >= output.at(0) && output.at(1) >= output.at(2)) //call
        {
            action.command = A_CALL;
        }
        else if(output.at(2) >= output.at(0) && output.at(2) >= output.at(1)) //raise
        {
            action.command = A_RAISE;
        }
        
    }
    
    return action;
}


